Skip to content

ci(release): decide changeset coverage from turbo's build hashes - #6

Merged
ryanleecode merged 6 commits into
mainfrom
ci/release-turbo-gate
Oct 13, 2026
Merged

ryanleecode merged 6 commits into
mainfrom
ci/release-turbo-gate

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The Changeset Check now decides which packages need an intent by turbo build hash, not by path. release.jsonc changes from { "strategy": "paths" } to { "strategy": "turbo", "task": "build" }. pnpm-release-management is pinned at b9b4a1d. At that pin, a publishable package needs an intent when its build task hash differs between the base and the head (process-adapter/src/change-verdict.ts verdictTouched, evidence from turbo-evidence.ts). systemfsoftware and pnpm-release-management already use the same gate.

The old comment said the turbo gate dry-runs turbo at the base and refuses a base without turbo.json. That is out of date: main has carried turbo.json since b7e907a (#2). .changeset/README.md now states the turbo rule. Its 0.0.0 caveat now keys on a change to the tarball rather than to the packages/api-extractor path.

.changeset/README.md:12-15 also says that files outside the package can move the hash, with measured examples (below), and that such a pull request names the package, with none when its tarball does not change.

No package, app or intent changes, and the shipped tarball is unchanged.

Turbo evidence (turbo 2.10.12, the catalog pin)

Gate proof

Each run is changeset-management check origin/main, using the release tools from this repo's dev shell (the b9b4a1d pin), against base 9bd7396, which has turbo.json. Each control was a throwaway local commit on top of this branch. The controls are deleted, and no base worktree was left behind.

Change on top of this head turbo gate paths gate
this PR alone exit 0, "no publishable package changed" (same, on main)
A: comment in packages/api-extractor/src/mod.ts exit 1, names @systemfsoftware/api-extractor —
B: turbo.json build.env gains a variable exit 1, names the package exit 0 (misses it)
C: comment in apps/api-extractor-e2e/outputs.ts (private) exit 0 —
D: PR #5 merged in exit 0 —
E: packages/api-extractor/stryker.config.ts only (not in files) exit 1 exit 1 (same path rule)

Under turbo build, any file in the package's default inputs counts as a change. E shows the cost: a change outside the tarball still needs an intent, and its intent is none. The paths gate behaves the same here, so this is not a regression.

CI on this head is the proof that the gate runs inside the devshell: true sandbox. No sibling caller runs turbo with devshell today: stryker-js-effect uses devshell with paths.

Credentials

The gate runs turbo run build --dry=json with no TURBO_TOKEN/TURBO_TEAM. ci.yml and changeset-check.yml pass neither, so turbo uses only the local cache and a public-repo run gets no credentials.

Code review (full path, report-only, final head ca70cc2)

ce-code-review mode:agent base:origin/main depth:full ran against head ca70cc232bbe8abb486862e4cb8e8356f8473c7a, base 6d9d72bb9554746fd798353aefb6ddbb5885b07e (main after #11). Run 20261013-202050-2791c2b1, an exact-head delta round after the merge of main below. CE verdict: Ready to merge. The review has no actionable finding. The one pack-rule breach is a departure the root accepted (below), and the PR waits for the root's verdict; it is not merged.

Rounds on this PR:

  • da6d7f2: full path, run …856eb364.
  • a2aa2cd: plain merge of origin/main (cd9f133, chore(packs): enroll ci-wall-clock and repin compound packs to 8d07118079 #8). The merge changes only .compound-engineering/config.yaml, which pins 8d071180798f9ea82ef5a4fd877152dacc02f6b6 and enrolls ci-wall-clock; nothing was rebased or force-pushed. Full-path delta round, run …d26930a4.
  • 12e09fe: README fix. Round …3e69dc8e is superseded, because its two findings were fixed in the next commit.
  • 0a72fe4: full path, run …84707681, Ready to merge.
  • bd4f9c9: plain merge of origin/main (a053ee6, test(api-extractor): retire the generated schema laws #9), with no rebase and no force-push. The merge brings exactly test(api-extractor): retire the generated schema laws #9's five files: .changeset/orange-bears-rush.md, the root package.json devDependency, pnpm-lock.yaml, packages/api-extractor/vitest.config.ts, and the deleted src/schema-laws.test.ts. git show --remerge-diff of the merge is empty, and this PR's own diff against main is unchanged (release.jsonc, .changeset/README.md). Exact-head delta round, run …6a9431bc: Ready to merge.
  • ca70cc2: plain merge of origin/main (6d9d72b, chore(packs): repin compound packs to systemfsoftware 0923912eed (#761) #11), with no rebase and no force-push. The merge brings one line, .compound-engineering/config.yaml:3, which now pins the packs to 0923912eedb46b34645a22d5d67f18b442c37919. git show --remerge-diff is empty, and this PR's diff against main is unchanged. Because the pack pin moved, this round judges the PR against the new rule set. Exact-head delta round: this receipt.

Packs: resolved from the ca70cc2 head's own .compound-engineering/config.yaml. --declared-only and the full resolve both exit 0 with no errors or warnings, giving five roots at 0923912eedb46b34645a22d5d67f18b442c37919. The packs hold 52 rule files: cell-architecture 15, boundary-testing 7, schema-laws 9, package-topology 7, ci-wall-clock 14. Applicability was decided by meaning (CONST-T12), and systemfsoftware CONSTITUTION.md (unchanged by the repin) outranks pack rules (CONST-S2).

Reviewers (ca70cc2 round): correctness, project standards (AGENTS.md plus the constitution), learnings with the per-rule receipt, reliability, security, and adversarial. All six ran as omp agents. Their session logs show only deepseek-v4.1-flash turns, with no Claude, Grok or xAI turn. The permitted non-Claude peer is omp's adversarial-reviewer on deepseek-v4.1-flash (21 turns); no external peer CLI is installed. No finding needed validation this round. In the bd4f9c9 round the validator ran on the same model (17 turns). No reviewer timed out: the longest slice took 6 m 16 s.

Declared breach: ci-wall-clock/light-checks-run-the-pinned-tool (accepted by root ruling)

  • Rule and case: ci-wall-clock/light-checks-run-the-pinned-tool, unchanged between the old pin 8d07118 and the new pin 0923912eed. The case is its "changeset check" case: this diff changes what the changeset check runs, which brings a workspace install into it. The rule: a changeset check runs its pinned tool directly, with no workspace install, dev shell or producer job before it, and the tool comes from one pinned fetch.
  • Breach: release.jsonc:13 ("gate": { "strategy": "turbo", "task": "build" }) makes the check take turbo from the workspace install. pnpm-release-management b9b4a1d packages/process-adapter/src/turbo-evidence.ts:27 reads TURBO_BIN = 'node_modules/.bin/turbo', and :106-116 refuses with TurboPinUnusable when it is absent. So this check can never run install-free; the paths strategy needed only git.
  • Root ruling, 2026-10-13, D1 = (a): the departure is accepted for this PR, and the cure is commissioned upstream.
  • Cure and owner: unit R, in systemfsoftware/pnpm-release-management: the turbo evidence takes turbo from one pinned fetch instead of the workspace install. After that ships, a later follow-up removes devshell: true at .github/workflows/changeset-check.yml:17 and repins pnpm-release-management, so the check runs install-free. Neither change belongs in this PR.
  • The job already bootstraps: changeset-check.yml:17 passes devshell: true, so the job already runs the dev shell and this repository's bootstrap before the verdict, on main as well. Measured: the check job took 7 m 44 s on da6d7f2 (run 38298009147) and 8 m 28 s on ci(mutation): run mutation testing on main through systemfsoftware's reusable workflow #5's head with the paths gate (run 38291945550); it took 6 m 39 s on 0a72fe4 (run 38376783369) and 7 m 13 s on bd4f9c9 (run 38450595183). On ca70cc2 (run 38462036008) the job took 17 m 02 s. From the log: building the pinned release-tools took 8 m 45 s and the dev-shell bootstrap took 6 m 10 s. The turbo verdict step took 12.6 s. Both slow steps run under the paths gate too. The bootstrap dominates every one of these runs, so the switch adds no measured wall time today. The breach is structural.
  • Going back to paths is closed: upstream #56 (merged as 2b89555) refuses the paths gate where turbo drives the comparison.

Findings and dispositions

Round # Sev File:line Finding Disposition
ca70cc2 1, 2 P2 release.jsonc:13 The check cannot run install-free, because turbo comes from the workspace install (adversarial; correctness and project standards). Accepted departure (D1 = a, above). Reliability and security raised nothing new.
ca70cc2 — — — ci-wall-clock/local-gate-equals-pr-gate (new at the pin): the changeset check is not in AGENTS.md's pre-push commands (adversarial, noted without a finding). Rejected as a finding: I ruled the rule a no match. The diff changes no root gate script or agent-instruction command, and adds no task, profile, environment switch or CI-only check. The changeset check was CI-only before this PR. Its new precondition, a workspace install, is the bootstrap AGENTS.md already documents.
bd4f9c9 1 P2 release.jsonc:13 The check takes turbo from the workspace install (correctness, project standards). Accepted departure (D1 = a, above).
bd4f9c9 2 P0 (conf 50) release.jsonc:13 The gate runs turbo resolved from the pull request's own lockfile (CWE-829, security). Rejected by the validator: this diff did not introduce it. changeset-check.yml:17 (devshell: true) already runs the pull request's own bootstrap before the verdict on main, and the release tools come from the pinned workflow, not the pull request. The workspace-turbo consequence is the D1 departure, cured by unit R.
bd4f9c9 3 P1 (conf 75) .changeset/README.md:12 An intent left pending by an earlier change, such as #9's orange-bears-rush.md now on main, satisfies the gate for a later pull request that moves the same package (adversarial). Rejected by the validator: this diff did not introduce it. The pinned tool counts every intent pending at the head under either strategy (gate-changes.ts:79-82 store.listIntents()), so paths behaves the same on main. A freshness guard that counts only intents the pull request adds would belong to pnpm-release-management. It is recorded here for that owner and not commissioned by this PR.
bd4f9c9 — P2 (conf 50, suppressed) release.jsonc:13 The turbo dry run has no timeout (reliability). Upstream, as in the 12e09fe row below.
0a72fe4 1 P2 (conf 50, advisory) .changeset/README.md:15 The check cannot tell whether a hash-moving change also changed the tarball, so a none intent is the author's judgement. Accepted by design; no change in this PR. While the version is 0.0.0, Release refuses a misjudged none with tag-integrity-mismatch, and the README's 0.0.0 bullet says so. Guidance in the refusal text belongs to pnpm-release-management (gate-changes.ts:104-118).
a2aa2cd — P2 .changeset/README.md:10 A change to a global input moves every package's hash, and the README did not say so. Two validators confirmed it. Fixed in 12e09fe and 0a72fe4: README.md:12-15 names measured examples.
a2aa2cd — P2 release.jsonc:13 The check takes turbo from the workspace install. Validator confirmed. Accepted departure (D1 = a, above).
a2aa2cd — P2 (conf 50, suppressed) release.jsonc:13 A build task that vanishes reads as unchanged (change-verdict.ts:35,54). Refuted at turbo 2.10.12: the probe kept the task with command <NONEXISTENT> and its hash moved.
12e09fe — P3 .changeset/README.md:13 The new list of inputs read as complete but omitted lockfile resolutions and other root dependency fields. Fixed in 0a72fe4: the inputs are now given as examples.
12e09fe — P2 (conf 50) release.jsonc:13 The turbo dry run has no timeout. Upstream. This repository's job only calls the reusable workflow, and GitHub does not allow timeout-minutes on such a job. The bound belongs in pnpm-release-management's changeset-check.yml and ProcessRun.ts.
da6d7f2 2 P2 release.jsonc:13 The gate depends on the workspace install. Rejected by that round's validator: the pinned check installs the workspace first.
da6d7f2 3 P3 1146 plan :244 KTD19 still says paths. D2 (does not block; the plan is frozen).

Pack receipt (ca70cc2, packs at 0923912eed)

Pack / rule Ruling At
ci-wall-clock / light-checks-run-the-pinned-tool violate, accepted departure (D1 = a) release.jsonc:13
ci-wall-clock / pr-gate-runs-only-what-the-diff-affects comply: the affected set comes from turbo's build graph instead of a hand-kept path list, and this receipt satisfies the rule's full-path review clause release.jsonc:13; .changeset/README.md:10-15

Not matched (50), each named in the learnings receipt and judged by meaning:

  • ci-wall-clock (12): failures-name-the-gate-and-location (new), flake-outputs-build-once-on-main, gates-share-one-job, heavy-lanes-state-their-daily-minutes, heavy-suites-judge-every-main-commit, local-gate-equals-pr-gate (new; ruled above), no-dev-shell-on-verdict-lanes (it covers lint, typecheck and test jobs), no-lane-grades-the-repos-own-docs, one-stuck-job-backstop, red-main-reverts-its-culprit-same-day, release-promotes-the-staging-proven-build, setup-under-a-minute.
  • boundary-testing (7, including the new expected-values-never-come-from-the-subjects-output), cell-architecture (15), schema-laws (9) and package-topology (7): every rule. The diff changes no code, test, schema, entry point, port, adapter or published surface.

The devshell: true at changeset-check.yml:17 predates this diff. light-checks-run-the-pinned-tool and setup-under-a-minute both refuse that shape on their own, and the unit R follow-up removes it.

Other items and their dispositions

Item Disposition
D2: the frozen 1146 plan's stale rows :244 (this PR's da6d7f2 finding 3) and :446 (#8's finding 5) D2 follow-up, owner A: a new dated plan that declares supersedes:. It does not block this PR.
No committed test exercises the gate's refusal; controls A-E and the probes were throwaway Evidence limitation: the refusal was shown with throwaway controls (Gate proof). This repository admits no test that only re-asserts the pinned tool's behaviour, and the Changeset Check runs the gate on every pull request.
The gate compares the base ref's tip, not the merge base Rejected as a defect: CI checks out the pull request's merge ref, which already contains the base tip, so the comparison isolates the pull request's own change. A local run against a stale ref is the only case that differs.
A later turbo.json change to build.inputs could narrow what the gate sees Rejected as a defect: turbo.json declares no build.inputs today. Any such change is itself a global input that moves every package's hash, so the gate asks for intents on that pull request and its review decides it.
Branches cut before this merges keep the paths gate until they merge main By design: each branch reads release.jsonc from its own head. #7 and #5 get the turbo gate at their next merge of main.

Checks at ca70cc2

  • Local, in the dev shell: pnpm run bootstrap exit 0. changeset-management check origin/main exit 0: "changeset gate: no publishable package changed (2 member(s))". pnpm lint and pnpm typecheck exit 0. turbo run test --force exit 0 with Cached: 0 cached, 2 total (api-extractor 5 files and 103 tests; e2e 1 of 1).
  • Pack resolver: --declared-only and full exit 0, errors and warnings none, five roots at 0923912eed.
  • Changeset Check run 38462036008: success.
  • CI run 38462036221: success, all 11 jobs. With the Changeset Check, 12 of 12 jobs are green on this head.
  • The PR has no review threads and no comments.
  • Earlier heads: bd4f9c9 (CI 38450595050, Changeset Check 38450595183) and 0a72fe4 (CI 38376783225, Changeset Check 38376783369), all green.

ce-compound

ce-compound was evaluated for this PR (mode non-interactive) and ended Documentation skipped. Re-evaluated at bd4f9c9 and ca70cc2: neither merge adds a new lesson, and the skip stands: the lesson worth keeping, install-free light checks versus turbo-native change evidence, waits on unit R's upstream fix.

Privacy

Grep at ca70cc2 over this PR's diff against main, both merge commit messages and this body: the 54 private systemfsoftware repository names, the required literal terms younna, younna-ai, Stead, wireblast (whole word, case-insensitive), host paths, and credential or remote-cache token names. Hits: none in the diff or the merge commit message. In this body, the only hits are this paragraph's own list, the file name AGENTS.md, the common word "global" (in "global input"), and TURBO_TOKEN/TURBO_TEAM, which the Credentials section names because the gate runs without them.

The release gate moves from `paths` to `{ strategy: turbo, task: build }`.
`main` has carried turbo.json since b7e907a, so the comment saying the turbo
gate refuses this repository's base is stale. A publishable package now needs
an intent when its turbo `build` hash moves between base and head
(pnpm-release-management b9b4a1d change-verdict.ts verdictTouched), the gate
systemfsoftware and pnpm-release-management use. .changeset/README.md states
the same rule and keys the 0.0.0 `none` caveat on the tarball.
The turbo gate asks for an intent when a package's `build` hash moves,
and files outside the package can move it. turbo 2.10.12's dry run moved
@systemfsoftware/api-extractor#build on a change to turbo.json's `build`
task, to `globalEnv`, and to the root package.json's dependencies, and
not on root metadata, a lockfile comment or another task's settings.
The README now names those inputs, so a contributor can see why such a
pull request owes an intent (`none` when the tarball is unchanged).
The list of outside-package inputs that move a package's `build` hash
named only the three that turbo 2.10.12's dry run was measured on, but
read as complete. Review named other inputs turbo also folds in (lockfile
resolutions, other root dependency fields). The sentence now gives the
measured inputs as examples; the gate itself compares turbo's hashes.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…ancestry; profile harness maps .cts and counts missing emits (U13 #5, #6)

The derived tsconfig sat in a bare temp directory, so tsc looked up a
`types` entry naming a package (`vitest/globals`) in node_modules
directories above the temp dir and refused with TS2688. Four corpus
packages the earlier profile modelled were refused this way. The
derived config is now written into a scoped mirror of the package
config's directory, each ancestor carrying a symlink to the real
node_modules, so tsc's upward walk matches the package's own. The
explicit typeRoots chain this replaces is gone; derivedTsconfig
becomes writeDerivedTsconfig, which writes the mirror and returns the
config path.

e2e: the equality journey gains a package whose tsconfig names
`types: ["greeting/globals"]` from its own node_modules through an
exports subpath. It fails at c4202d9 with TS2688 and passes after.

Profile harness: .cts sources map to .d.cts and are discovered; null
and array export targets are skipped; a file missing from the 5.9.3
output is counted apart from a printing difference; tsc's stderr is
drained alongside stdout.
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…mirrored node_modules learning

Supersedes the 1834 plan. U13 rules every finding of the full-path
review at e5ec09d: six fixed (#1, #2, #4, #5, #6, #8), the
mapped-type/infer anchor-50 note fixed after reproduction, the
TS2688 regression the rerun found fixed, #3 and #7 rejected with the
validator's reasons, and the Diagnostic.code refusal note made a
PR4 follow-up. KTD3 now describes the mirrored node_modules ancestry
instead of the explicit typeRoots chain, and the policy gains
isolatedDeclarations: false.

docs/profiles/2026-10-13-front-end-phases: the U13 harness at 432b608
on the corpus at 732f66a0c8. 126 of 585 shared declaration files print
differently between TypeScript 7 and 5.9.3 and none are missing; every
package's route and outcome match the second profile.

The emit-option learning gains the isolatedDeclarations case and the
types-from-node_modules case, each with its gate.

@ryanleecode ryanleecode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Root verdict at ca70cc2: 12/12 green, 0 behind, 0 threads; exact-head DeepSeek receipt with 5 roots at 0923912eed; the light-checks-run-the-pinned-tool breach at release.jsonc:13 is the root-ruled declared departure (D1=a, upstream cure commissioned to pnpm-release-management); every finding dispositioned; ce-compound evaluated; privacy clean. Retires the banned paths classifier for turbo's build-hash gate.

@ryanleecode
ryanleecode merged commit 942ac81 into main Oct 13, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants